package ott.neumont;

import java.math.*;

import javax.jws.*;

import org.springframework.beans.factory.annotation.*;

import ott.webapp.*;

/**
 * Service that implements the contract
 * @author Caleb
 *
 */
public class Decrypt implements IDecrypt {

	@Autowired
	private IFactorService factorService;

	@WebMethod(action = "/decryptKey")
	public KeyParts decryptKey(@WebParam(name = "key") Key publicKey) {
		// gets the 'n' value out of the public key
		long n = publicKey.getN();
		// gets all the factors out of the number 'n'
		// uses factor service from "rest" assignment
		Factors nFactors = factorService.findFactors(n);
		// calls a method to determine prime factors
		Factors primes = find2PrimeFactors(nFactors, n);
		
		// turns the 'e' into a big integer object
		BigInteger e = new BigInteger(String.valueOf(publicKey.getM()));
		// multiplies two factors together to get the 'r' value (in long type)
		long rLong = (primes.getFactors()[0].getFactor() - 1) * (primes.getFactors()[1].getFactor() - 1);
		// turns r into a big integer object
		BigInteger r = new BigInteger(String.valueOf(rLong));
		
		// uses mod Inverse to get the value of 'd'
		BigInteger d = e.modInverse(r);
		
		// builds the private key off the now known 'd'
		Key privatekey = new Key(n, d.longValue());
		// re-combines the two key parts (adding the newly discovered private key) and returning it
		return new KeyParts(privatekey, publicKey);
	}

	private Factors find2PrimeFactors(Factors nFactors, long n) {
		
		for (Factor f : nFactors.getFactors()) { // for each factor
			long current = f.getFactor();
			if (1 < current) { // that is higher than 1 
				if (isPrimeNumber((int) current)) { // if its prime
					for (Factor f2 : nFactors.getFactors()) { // for each factor
						long newCurrent = f2.getFactor();
						if (1 < newCurrent && newCurrent != current // that is greater than 1 and isn't the same as the previous factor
								&& newCurrent * current == n && isPrimeNumber((int) newCurrent)) { // and multiplies with the first factor to equal 'n' and is also prime
							Factors factors = new Factors(new Factor[] { // combine two correct prime factors into an object
									new Factor(current, null),
									new Factor(newCurrent, null) });
							return factors;
						}
					}
				}
			}
		}
		
		// if algorithm runs and does not return - then two factors have not been found
		// throw exception since 'n' should have two prime factors
		throw new RuntimeException("Does not have two prime factors");
	}

	public static boolean isPrimeNumber(int num) {
		assert num > -1;
		for (int i = 2; i < num; i++) {
			if (GCF(i, num) != 1)
				return false;
		}
		return true;
	}

	public static int GCF(int a, int b) {
		if (b == 0)
			return a;
		else
			return GCF(b, a % b);
	}

}
